package http

import (
	"errors"
	"fmt"
	"github.com/gofiber/contrib/fiberzerolog"
	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/cors"
	"github.com/gofiber/fiber/v2/middleware/csrf"
	"github.com/gofiber/fiber/v2/middleware/recover"
	websocket "github.com/gofiber/websocket/v2"
	"github.com/rs/zerolog/log"
	"shyxy-model-agent/core/application"
	"shyxy-model-agent/core/http/middleware"
	"shyxy-model-agent/core/http/routes"
	"shyxy-model-agent/core/http/utils"
	"shyxy-model-agent/core/schema"
)

func API(application *application.Application) (*fiber.App, error) {

	fiberCfg := fiber.Config{
		BodyLimit:             1024 * 1024,
		DisableStartupMessage: true,
	}

	if !application.ApplicationConfig().OpaqueErrors {
		// Normally, return errors as JSON responses
		fiberCfg.ErrorHandler = func(ctx *fiber.Ctx, err error) error {
			// Status code defaults to 500
			code := fiber.StatusInternalServerError

			// Retrieve the custom status code if it's a *fiber.Error
			var e *fiber.Error
			if errors.As(err, &e) {
				code = e.Code
			}

			// Send custom error page
			return ctx.Status(code).JSON(
				schema.ErrorResponse{
					Error: &schema.APIError{Message: err.Error(), Code: code},
				},
			)
		}
	} else {
		// If OpaqueErrors are required, replace everything with a blank 500.
		fiberCfg.ErrorHandler = func(ctx *fiber.Ctx, _ error) error {
			return ctx.Status(500).SendString("")
		}
	}
	router := fiber.New(fiberCfg)

	if application.ApplicationConfig().MachineTag != "" {
		router.Use(func(c *fiber.Ctx) error {
			c.Response().Header.Set("Machine-Tag", application.ApplicationConfig().MachineTag)

			return c.Next()
		})
	}

	router.Use("/v1/realtime", func(c *fiber.Ctx) error {
		if websocket.IsWebSocketUpgrade(c) {
			// Returns true if the client requested upgrade to the WebSocket protocol
			return c.Next()
		}

		return nil
	})
	router.Hooks().OnListen(func(listenData fiber.ListenData) error {
		scheme := "http"
		if listenData.TLS {
			scheme = "https"
		}
		log.Info().Str("endpoint", scheme+"://"+listenData.Host+":"+listenData.Port).Msg("Model-Agent API is listening! Please connect to the endpoint for API documentation.")
		return nil
	})

	// Have Fiber use zerolog like the rest of the application rather than it's built-in logger
	logger := log.Logger
	router.Use(fiberzerolog.New(fiberzerolog.Config{
		Logger: &logger,
	}))

	// Default middleware config

	if !application.ApplicationConfig().Debug {
		router.Use(recover.New())
	}

	// Health Checks should always be exempt from auth, so register these first
	routes.HealthRoutes(router)
	kaConfig, err := middleware.GetKeyAuthConfig(application.ApplicationConfig())
	if err != nil || kaConfig == nil {
		return nil, fmt.Errorf("failed to create key auth config: %w", err)
	}

	allowHeaders := "*"
	if application.ApplicationConfig().CORS {
		var c func(ctx *fiber.Ctx) error
		if application.ApplicationConfig().CORSAllowOrigins == "" {
			c = cors.New(cors.Config{
				AllowOrigins:     cors.ConfigDefault.AllowOrigins,
				AllowMethods:     cors.ConfigDefault.AllowMethods,
				AllowHeaders:     allowHeaders,
				AllowCredentials: cors.ConfigDefault.AllowCredentials,
			})
		} else {
			c = cors.New(cors.Config{AllowOrigins: application.ApplicationConfig().CORSAllowOrigins,
				AllowMethods:     cors.ConfigDefault.AllowMethods,
				AllowHeaders:     allowHeaders,
				AllowCredentials: cors.ConfigDefault.AllowCredentials,
			})
		}

		router.Use(c)
	}
	if application.ApplicationConfig().CSRF {
		log.Debug().Msg("Enabling CSRF middleware. Tokens are now required for state-modifying requests")
		router.Use(csrf.New())
	}

	requestExtractor := middleware.NewRequestExtractor(application.DBLoader(), application.ModelLoader(), application.ApplicationConfig())

	routes.RegisterOpenAIRoutes(router, requestExtractor, application)
	routes.RegisterTestRoutes(router, application)
	router.Use(notFoundHandler)
	return router, nil
}

func notFoundHandler(c *fiber.Ctx) error {
	// Check if the request accepts JSON
	if string(c.Context().Request.Header.ContentType()) == "application/json" || len(c.Accepts("html")) == 0 {
		// The client expects a JSON response
		return c.Status(fiber.StatusNotFound).JSON(schema.ErrorResponse{
			Error: &schema.APIError{Message: "Resource not found", Code: fiber.StatusNotFound},
		})
	} else {
		// The client expects an HTML response
		return c.Status(fiber.StatusNotFound).Render("views/404", fiber.Map{
			"BaseURL": utils.BaseURL(c),
		})
	}
}
